package next.fire.spinus.logx.base;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import next.fire.spinus.logx.utils.http.FireHttpUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by daibing on 2020/9/15.
 */
public abstract class AbstractClient {
    private static final Integer HTTP_TIMEOUT = 30 * 1000;
    private static final Integer RETRY_NUMBER = 3;

    public enum Method {
        GET, PUT, POST, DELETE,;
    }

    public final <T> T execute(Method method, String uri, String providerId, boolean needRetry,
                               boolean skipCheckBodyStatus, RequestHandler<? extends T> handler) {
        // 1. prepare params data
        Map<String, Object> bizParams = handler.buildBizParams();
        Map<String, Object> accessParams = handler.buildAccessParams();

        // 2. build http request data
        byte[] rawBody = this.buildRawBody(uri, providerId, bizParams, accessParams);
        byte[] encodeBody = this.buildEncodeBody(uri, providerId, bizParams, accessParams, rawBody);
        Map<String, String> header = this.buildHeader(method, uri, providerId, bizParams, accessParams, rawBody, encodeBody);
        String sign = this.buildSign(method, uri, providerId, bizParams, accessParams, header, rawBody, encodeBody);
        this.loadSign(providerId, header, sign);

        // 3. send http request
        String url = this.buildUrl(uri, providerId, bizParams, accessParams, sign);
        Map<String, String> result = this.doRunByRetry(method, url, header, encodeBody, needRetry);

        // 4. parse response data
        JSONObject output = this.parse(result, accessParams);
        boolean pass = this.checkBody(output, skipCheckBodyStatus);
        if (!pass) {
            throw new RuntimeException("Platform response data is not ok: " + uri + ", " + result);
        }
        boolean safe = this.checkSign(providerId, result, output);
        if (!safe) {
            throw new RuntimeException("Platform response data is not safe: " + uri + ", " + result);
        }
        return handler.extractData(output);
    }

    protected abstract byte[] buildRawBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams);

    protected abstract byte[] buildEncodeBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, byte[] rawBody);

    /**
     * default header, override by sub-class
     */
    protected Map<String, String> buildHeader(Method method, String uri, String providerId, Map<String, Object> bizParams,
                                              Map<String, Object> accessParams, byte[] rawBody, byte[] encodeBody) {
        Map<String, String> header = new HashMap<>(1);
        header.put("Content-Type", "application/x-www-form-urlencoded; charset=" + StandardCharsets.UTF_8.name());
        return header;
    }

    protected abstract String buildSign(Method method, String uri, String providerId, Map<String, Object> bizParams,
                                        Map<String, Object> accessParams, Map<String, String> header, byte[] rawBody, byte[] encodeBody);

    protected abstract void loadSign(String providerId, Map<String, String> header, String sign);

    protected abstract String buildUrl(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, String sign);

    /**
     * default parse json, override by sub-class
     */
    protected JSONObject parse(Map<String, String> postResult, Map<String, Object> accessParams) {
        return JSON.parseObject(postResult.get(FireHttpUtils.RESPONSE_BODY));
    }

    protected abstract boolean checkBody(JSONObject output, boolean skipCheckBodyStatus);

    protected abstract boolean checkSign(String providerId, Map<String, String> rawOutput, JSONObject output);

    private Map<String, String> doRunByRetry(Method method, String url, Map<String, String> header, byte[] body, boolean needRetry) {
        if (!needRetry) {
            return this.doRun(method, url, header, body);
        }
        for (int i = 0; i < RETRY_NUMBER; i++) {
            try {
                return this.doRun(method, url, header, body);
            } catch (Exception e) {
                if (RETRY_NUMBER - 1 == i) {
                    throw e;
                }
            }
        }
        return new HashMap<>();
    }

    private Map<String, String> doRun(Method method, String url, Map<String, String> header, byte[] bodyBytes) {
        Map<String, String> result = null;
        try {
            if (Method.GET == method && bodyBytes.length > 0) {
                url += "?" + new String(bodyBytes);
            }
            FireHttpUtils httpClient = new FireHttpUtils(url, HTTP_TIMEOUT, true);
            switch (method) {
                case GET:
                    result = httpClient.get(header);
                    break;
                case PUT:
                    result = httpClient.put(header, bodyBytes);
                    break;
                case POST:
                    result = httpClient.post(header, bodyBytes);
                    break;
                case DELETE:
                    result = httpClient.delete(header, bodyBytes);
                    break;
                default:
            }
        } catch (IOException e) {
            Map<String, String> special = this.specialExceptionHandle(e.getMessage());
            if (special != null && !special.isEmpty()) {
                return special;
            }
            throw new RuntimeException(String.format("Provider client http %s failed: url：%s, exception: %s", method, url, e));
        }
        return result;
    }

    protected Map<String, String> specialExceptionHandle(String message) {
        return null;
    }

    public static boolean isBlank(String s) {
        return s == null || s.trim().length() == 0;
    }
}
